package com.demo2;

import com.alibaba.fastjson.JSONObject;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.SystemPropertyHook;
import com.github.unidbg.linux.android.SystemPropertyProvider;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.api.ApplicationInfo;
import com.github.unidbg.linux.android.dvm.api.PackageInfo;
import com.github.unidbg.linux.android.dvm.api.SystemService;
import com.github.unidbg.linux.android.dvm.array.ArrayObject;
import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.virtualmodule.android.MediaNdkModule;
import java.io.File;
import java.util.*;

public class Tiny extends AbstractJni implements IOResolver {
    @Override
    public FileResult resolve(Emulator emulator, String pathname, int oflags) {
        return null;
    }

    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
    private final DvmClass hybrid;
    private JSONObject tiStoreSpData;

    Tiny(){
        // 创建模拟器实例
        emulator = AndroidEmulatorBuilder
                .for64Bit()
                .addBackendFactory(new Unicorn2Factory(true))
                .setProcessName("com.xingin.xhs")
                .build();

        // 模拟器的内存操作接口
        final Memory memory = emulator.getMemory();
        // 设置系统类库解析
        memory.setLibraryResolver(new AndroidResolver(23));
        // 创建 Android 虚拟机代理
        vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/demo2/小红书7_38.apk"));
        // 开启多线程
        emulator.getSyscallHandler().addIOResolver(this);
        emulator.getSyscallHandler().setEnableThreadDispatcher(true);
        emulator.getBackend().registerEmuCountHook(100000); // 设置执行多少条指令切换一次线程
        // 加载 libmedia.so 虚拟模块
        new MediaNdkModule(emulator, vm).register(memory);
        // 处理属性访问
        SystemPropertyHook systemPropertyHook = new SystemPropertyHook(emulator);
        systemPropertyHook.setPropertyProvider(new SystemPropertyProvider() {
            @Override
            public String getProperty(String key) {
                System.out.println("lilac systemkey:" + key);
                switch (key) {
                    case "ro.board.platform":{
                        return "sdm845";
                    }
                    case "ro.build.date.utc":{
                        return "1604422370";
                    }
                    case "ro.build.display.id":{
                        return "QKQ1.190828.002 test-keys";
                    }
                    case "ro.build.fingerprint":{
                        return "Xiaomi/polaris/polaris:10/QKQ1.190828.002/V12.0.2.0.QDGCNXM:user/release-keys";
                    }
                    case "ro.build.host":{
                        return "c3-miui-ota-bd134.bj";
                    }
                    case "ro.build.id":{
                        return "QKQ1.190828.002";
                    }
                    case "ro.build.tags":{
                        return "release-keys";
                    }
                    case "ro.build.type":{
                        return "user";
                    }
                    case "ro.build.version.incremental":{
                        return "V12.0.2.0.QDGCNXM";
                    }
                    case "ro.build.version.release":{
                        return "10";
                    }
                    case "ro.build.version.sdk":{
                        return "29";
                    }
                    case "ro.build.version.security_patch":{
                        return "2020-09-01";
                    }
                    case "ro.product.board":{
                        return "sdm845";
                    }
                    case "ro.product.brand":{
                        return "Xiaomi";
                    }
                    case "ro.product.cpu.abilist":{
                        return "arm64-v8a,armeabi-v7a,armeabi";
                    }
                    case "ro.product.device":{
                        return "polaris";
                    }
                    case "ro.product.manufacturer":{
                        return "Xiaomi";
                    }
                    case "ro.product.model":{
                        return "MIX 2S";
                    }
                    case "ro.product.name":{
                        return "polaris";
                    }
                    case "gsm.version.baseband":{
                        return "4.0.c2.6-00335-0914_2350_3c8fca6,4.0.c2.6-00335-0914_2350_3c8fca6";
                    }
                    // adb detect
                    case "sys.usb.state":{
                        return "";
                    }
                    case "sys.usb.config":{
                        return "";
                    }
                    case "ro.boot.hardware":{
                        return "qcom";
                    }
                }
                return "";
            }

            ;
        });
        memory.addHookListener(systemPropertyHook);

        // 设置 JNI 和 日志
        vm.setJni(this);
        vm.setVerbose(true);

        DalvikModule dm = vm.loadLibrary("tiny", true);
        module = dm.getModule();
        vm.callJNI_OnLoad(emulator, module);
        hybrid = vm.resolveClass("com/xingin/tiny/internal/Hybrid");

        tiStoreSpData = new JSONObject();
        tiStoreSpData.put("launch_count", "1");
        tiStoreSpData.put("last_launch_time", "1654827889900");
        tiStoreSpData.put("local_id", "57588196a21ca998609121ce52b6f47b");
        tiStoreSpData.put("uniform_id", "00000000421f9a44a2b63e780d11b3d1b9b8a7e6");
        tiStoreSpData.put("first_launch_time", "1654827889900");
    }

    public void init(){
        ArrayObject obj_init_array = new ArrayObject(new StringObject(vm, "ECFAAF01"), new StringObject(vm, "xhs_ditui_tv2_000082"));
        System.out.println("********************* Init 1 *********************");
        hybrid.callStaticJniMethodObject(emulator, "callNative(I[Ljava/lang/Object;)Ljava/lang/Object;", 0, obj_init_array);
        System.out.println("********************* Init 2 *********************");
        hybrid.callStaticJniMethodObject(emulator, "callNative(I[Ljava/lang/Object;)Ljava/lang/Object;", 2, new ArrayObject());
    }

    public void callTarget() {
        System.out.println("********************* target *********************");
        ArrayObject objArray1 = new ArrayObject(new StringObject(vm, "GET"), new StringObject(vm, "https://edith.xiaohongshu.com/api/sns/v2/system_service/octopus_dns"), null);
        DvmObject ret = hybrid.callStaticJniMethodObject(emulator, "callNative(I[Ljava/lang/Object;)Ljava/lang/Object;", 1, objArray1);
        System.out.println("result:"+ret.getValue().toString());
    }

    public static void main(String[] args) {
        Tiny ty = new Tiny();
        ty.init();
        ty.callTarget();
    }

    @Override
    public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature){
            case "com/xingin/tiny/internal/Hybrid->callJava(I[Ljava/lang/Object;)Ljava/lang/Object;":{
                int i = vaList.getIntArg(0);
                System.out.println("lilac callJava:"+i);
                switch (i){
                    case 0:
                        DvmObject context = vm.resolveClass("android/content/Context").newObject(null);
                        vm.addGlobalObject(context);
                        return context;
                    case 1:
                        return new PackageInfo(vm, "com.xingin.xhs", 0);
                    case 2:
                        // // Intent { act=android.intent.action.BATTERY_CHANGED flg=0x60000010 (has extras) }
                        return vm.resolveClass("android/content/Intent").newObject(null);
                    case 3:
                        return new StringObject(vm, "1080,1920,420");
                    case 4:
                        return new StringObject(vm, "wifi");
                }
            }
            case "android/provider/Settings$Secure->getString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;":{
                String key = vaList.getObjectArg(1).getValue().toString();
                System.out.println(key);
                switch (key){
                    case "android_id":{
                        return new StringObject(vm, "6cc1d12a397a7c32");
                    }
                    case "default_input_method":{
                        return new StringObject(vm, "com.google.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME");
                    }
                    case "enabled_input_methods":{
                        return new StringObject(vm, "com.google.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME:com.google.android.googlequicksearchbox/com.google.android.voicesearch.ime.VoiceInputMethodService");
                    }
                    case "enabled_accessibility_services":{
                        return null;
                    }
                    case "bluetooth_address":{
                        return new StringObject(vm, "40:4E:36:46:42:8D");
                    }
                }
            }
        }
        return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
    }

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature){
            case "android/content/Context->getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences;":{
                String tableName = vaList.getObjectArg(0).getValue().toString();
                System.out.println("open SP:"+tableName);
                return vm.resolveClass("android/content/SharedPreferences").newObject(tableName);
            }
            case "android/content/SharedPreferences->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;":{
                String tableName = dvmObject.getValue().toString();
                if(tableName.equals("ti.store")){
                    String key = vaList.getObjectArg(0).getValue().toString();
                    return new StringObject(vm, tiStoreSpData.getString(key));

                }
            }
            case "android/content/SharedPreferences->edit()Landroid/content/SharedPreferences$Editor;":{
                return vm.resolveClass("android/content/SharedPreferences$Editor").newObject(null);
            }
            case "android/content/SharedPreferences$Editor->putString(Ljava/lang/String;Ljava/lang/String;)Landroid/content/SharedPreferences$Editor;":{
                String key = vaList.getObjectArg(0).getValue().toString();
                String value = vaList.getObjectArg(1).getValue().toString();
                // 更新表
                tiStoreSpData.put(key, value);
                return dvmObject;
            }
            case "android/content/Context->getContentResolver()Landroid/content/ContentResolver;":{
                return vm.resolveClass("android/content/ContentResolver").newObject(null);
            }
            case "android/content/Context->getSystemService(Ljava/lang/String;)Ljava/lang/Object;":{
                String serviceName = vaList.getObjectArg(0).getValue().toString();
                if(serviceName.equals("activity")){
                    // android/app/ActivityManager
                    return vm.resolveClass("android/app/ActivityManager").newObject(null);
                }
                return new SystemService(vm, serviceName);
            }
            case "android/telephony/TelephonyManager->getSimOperator()Ljava/lang/String;":{
                // 移动网络运营商名称
                return new StringObject(vm, "46003");
            }
            case "android/telephony/TelephonyManager->getSimOperatorName()Ljava/lang/String;":{
                return new StringObject(vm, "中国电信");
            }
            case "java/util/Map->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;":{
                HashMap<Object, Object> map = (HashMap) dvmObject.getValue();
                String key = vaList.getObjectArg(0).getValue().toString();
                String value = vaList.getObjectArg(1).getValue().toString();
                map.put(key, value);
                return ProxyDvmObject.createObject(vm, map);
            }
            case "android/net/wifi/WifiManager->getConnectionInfo()Landroid/net/wifi/WifiInfo;":{
                return vm.resolveClass("android/net/wifi/WifiInfo").newObject(null);
            }
            case "android/net/wifi/WifiInfo->getSSID()Ljava/lang/String;":{
                return new StringObject(vm, "<unknown ssid>");
            }
            case "android/net/wifi/WifiInfo->getBSSID()Ljava/lang/String;":{
                return new StringObject(vm, "02:00:00:00:00:00");
            }
            case "android/net/wifi/WifiInfo->getMacAddress()Ljava/lang/String;":{
                return new StringObject(vm, "02:00:00:00:00:00");
            }
            // 传感器列表补起来太无聊了，我偷懒了
            case "android/hardware/SensorManager->getSensorList(I)Ljava/util/List;":{
                int length = 10;
                List<DvmObject<?>> SensorList = new ArrayList<>();
                for (int i = 0; i < length; i++) {
                    SensorList.add(vm.resolveClass("android/hardware/Sensor").newObject(i));
                }
                return vm.resolveClass("java/util/ArrayList", vm.resolveClass("java/util/Collection")).newObject(SensorList);
            }
            case "android/telephony/TelephonyManager->getImei(I)Ljava/lang/String;":{
                // 自行设置 iemi
                return new StringObject(vm,"imei");
            }
            case "android/content/pm/PackageManager->getInstallerPackageName(Ljava/lang/String;)Ljava/lang/String;":{
                String packageName = vaList.getObjectArg(0).getValue().toString();
                System.out.println(packageName);
                // 最好处理成某个应用商城渠道
                if(packageName.equals("com.xingin.xhs")){
                    return new StringObject(vm,vm.getPackageName());
                }
            }
            case "android/content/Intent->addCategory(Ljava/lang/String;)Landroid/content/Intent;":{
                return dvmObject;
            }
            case "android/content/pm/PackageManager->queryIntentActivities(Landroid/content/Intent;I)Ljava/util/List;":{
                return null;
            }
            case "java/util/Collection->iterator()Ljava/util/Iterator;":{
                Collection arrayList = (Collection) dvmObject.getValue();
                return vm.resolveClass("java/util/Iterator").newObject(arrayList.iterator());
            }
            case "java/util/Iterator->next()Ljava/lang/Object;":{
                Iterator iterator = (Iterator) dvmObject.getValue();
                return vm.resolveClass("android/hardware/Sensor").newObject(iterator.next());
            }
            case "android/hardware/Sensor->getName()Ljava/lang/String;":{
                return new StringObject(vm, "sensorName");
            }
            case "android/hardware/Sensor->getVendor()Ljava/lang/String;":{
                // 自行处理传感器的名字
                return new StringObject(vm, "vendorName");
            }
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }

    @Override
    public void callVoidMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature){
            case "android/content/SharedPreferences$Editor->apply()V":{
                // 这里处理的并不地道，没有把对 SP 所作的修改持久化
                return;
            }
            case "android/app/ActivityManager->getMemoryInfo(Landroid/app/ActivityManager$MemoryInfo;)V":{
                return;
            }
        }
        super.callVoidMethodV(vm, dvmObject, signature, vaList);
    }

    @Override
    public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
        switch (signature){
            case "android/content/pm/PackageInfo->packageName:Ljava/lang/String;":{
                return new StringObject(vm, vm.getPackageName());
            }
            case "android/content/pm/PackageInfo->applicationInfo:Landroid/content/pm/ApplicationInfo;":{
                return new ApplicationInfo(vm);
            }
            case "android/content/pm/ApplicationInfo->sourceDir:Ljava/lang/String;":{
                return new StringObject(vm, "/data/app/com.xingin.xhs-b3jv2cFJTeUP8YDQwN8R9A==/base.apk");
            }
            case "android/content/pm/ApplicationInfo->dataDir:Ljava/lang/String;":{
                return new StringObject(vm, "/data/user/0/com.xingin.xhs");
            }
        }
        return super.getObjectField(vm, dvmObject, signature);
    }

    @Override
    public int getIntField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
        switch (signature){
            case "android/content/pm/PackageInfo->versionCode:I":{
                return (int) vm.getVersionCode();
            }
            case "android/content/pm/ApplicationInfo->targetSdkVersion:I":{
                return 23;
            }
        }
        return super.getIntField(vm, dvmObject, signature);
    }

    @Override
    public long getLongField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
        switch (signature){
            case "android/content/pm/PackageInfo->firstInstallTime:J":{
                return 1653383213230L;
            }
            case "android/content/pm/PackageInfo->lastUpdateTime:J":{
                return 1653474157540L;
            }
            case "android/app/ActivityManager$MemoryInfo->totalMem:J":{
                return 8164116000L;
            }
            case "android/app/ActivityManager$MemoryInfo->availMem:J":{
                return 5716304000L;
            }
        }
        return super.getLongField(vm, dvmObject, signature);
    }

    @Override
    public int callStaticIntMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature){
            case "android/provider/Settings$Global->getInt(Landroid/content/ContentResolver;Ljava/lang/String;)I":{
                String key = vaList.getObjectArg(1).getValue().toString();
                System.out.println("Settings$Global get:"+key);
                // 检测 adb
                if(key.equals("adb_enabled")){
                    return 0;
                }
                if(key.equals("development_settings_enabled")){
                    return 1;
                }
            }
            case "android/provider/Settings$Secure->getInt(Landroid/content/ContentResolver;Ljava/lang/String;)I":{
                String key = vaList.getObjectArg(1).getValue().toString();
                System.out.println("Settings$Secure get:"+key);
                if(key.equals("accessibility_enabled")){
                    return 0;
                }
                if(key.equals("location_mode")){
                    return 0;
                }
            }
            case "android/provider/Settings$System->getInt(Landroid/content/ContentResolver;Ljava/lang/String;)I":{
                String key = vaList.getObjectArg(1).getValue().toString();
                System.out.println("Settings$System get:"+key);
                if(key.equals("screen_brightness")){
                    // https://developer.android.com/reference/android/provider/Settings.System#SCREEN_BRIGHTNESS
                    return 45;
                }
            }
        }
        return super.callStaticIntMethodV(vm, dvmClass, signature, vaList);
    }

    @Override
    public boolean callBooleanMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature){
            // 这个Intent大概是 ACTION_BATTERY_CHANGED
            case "android/content/Intent->getBooleanExtra(Ljava/lang/String;Z)Z":{
                String key = vaList.getObjectArg(0).getValue().toString();
                if(key.equals("present")){
                    return false;
                }
            }
        }
        return super.callBooleanMethodV(vm, dvmObject, signature, vaList);
    }

    @Override
    public int callIntMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature){
            // 电池相关
            case "android/content/Intent->getIntExtra(Ljava/lang/String;I)I":{
                String key = vaList.getObjectArg(0).getValue().toString();
                switch (key){
                    case "status":
                        return 5;
                    case "scale":
                        return 100;
                    case "level":
                        return 100;
                    case "plugged":
                        return 2;
                }
            }
            case "android/telephony/TelephonyManager->getSimState()I":{
                // sim卡状态
                return 1;
            }
            case "android/content/Context->checkPermission(Ljava/lang/String;II)I":{
                String permissionName = vaList.getObjectArg(0).getValue().toString();
                System.out.println(permissionName);
                if(permissionName.equals("android.permission.ACCESS_WIFI_STATE")){
                    return 0;
                }
                if(permissionName.equals("android.permission.ACCESS_COARSE_LOCATION")){
                    return 0;
                }
                if(permissionName.equals("android.permission.ACCESS_FINE_LOCATION")){
                    return 0;
                }
            }
            case "android/net/wifi/WifiManager->getWifiState()I":{
                return 1;
            }
            //  WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
            //  WifiInfo wifiInfo = wifiManager.getConnectionInfo();
            case "android/net/wifi/WifiInfo->getIpAddress()I":{
                return 134326464;
            }
            case "android/hardware/Sensor->getType()I":{
                return 3;
            }
            case "android/hardware/Sensor->getVersion()I":{
                return 2;
            }
        }
        return super.callIntMethodV(vm, dvmObject, signature, vaList);
    }


    @Override
    public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature){
            case "java/util/HashMap-><init>()V":{
                return vm.resolveClass("java/util/Map").newObject(new HashMap<>());
            }
            case "android/content/Intent-><init>(Ljava/lang/String;)V":{
                String actionName = vaList.getObjectArg(0).getValue().toString();
                return dvmClass.newObject(actionName);
            }
            case "android/app/ActivityManager$MemoryInfo-><init>()V":{
                return vm.resolveClass("android/app/ActivityManager$MemoryInfo").newObject(signature);
            }
        }
        return super.newObjectV(vm, dvmClass, signature, vaList);
    }
}
